package xgen;

import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;

import java.io.PrintStream;
import java.util.*;

public class Table {
    private final String name;
    private String lock;
    private final String key;
    private final String value;
    private final boolean autoIncrement;
    private final boolean isMemory;
    private final String capacity;
    private Type keytype;
    private Type valuetype;

    public Table(Element self) {
        name = self.getAttribute("name").trim();
        if (!name.toLowerCase().equals(name)) {
            throw new RuntimeException("tablename MUSTBE lowercase");
        }
        Main.verifyName(name);

        Define define = Define.getInstance();

        lock = self.getAttribute("lock");
        if (lock == null) {
            throw new RuntimeException("not found lock in table '" + name + "'");
        } else if (lock.equals("")) {
            lock = name;
        } else if (define.isParsed() && !Define.getInstance().isLockname(lock)) {
            throw new RuntimeException("undefined lockname '" + lock + "' in table '" + name + "'");
        }

        if (define.isParsed()) {
            if (self.hasAttribute("cachelow")) {
                throw new RuntimeException("cachelow is not allowed for defined xdb in table '" + name + "'");
            }
            if (self.hasAttribute("cachehigh")) {
                throw new RuntimeException("cachehigh is not allowed for defined xdb in table '" + name + "'");
            }
            String cachecap = self.getAttribute("cacheCapacity");
            if (cachecap != null && define.getCacheCap(cachecap) == null) {
                throw new RuntimeException("undefined cacheCapacity '" + cachecap + "' in table '" + name + "'");
            }
            String cachepage = self.getAttribute("cachePage");
            if (cachepage != null && define.getCachePage(cachepage) == null) {
                throw new RuntimeException("undefined cachePage '" + cachepage + "' in table '" + name + "'");
            }
        }

        key = self.getAttribute("key").trim();
        value = self.getAttribute("value").trim();
        isMemory = self.getAttribute("persistence").equalsIgnoreCase("MEMORY");

        autoIncrement = self.getAttribute("autoIncrement").equals("true");
        capacity = self.getAttribute("capacity").trim();
    }

    static void make(Collection<Table> tables) {
        String classname = "_Tables_";
        PrintStream ps = Main.openTableFile(classname);
        List<Table> lsTables = new ArrayList<Table>(tables);
        Collections.sort(lsTables, new Comparator<Table>() {
            @Override
            public int compare(Table t1, Table t2) {
                return t1.getName().compareTo(t2.getName());
            }
        });

        ps.println("package xtable;");
        ps.println();
        if (!tables.isEmpty()) {
            ps.println("import com.goldhuman.Common.Marshal.OctetsStream;");
            ps.println("import com.goldhuman.Common.Marshal.MarshalException;");
        }
        ps.println();
        ps.println("public class _Tables_ extends xdb.Tables {");
        ps.println("	static volatile boolean isExplicitLockCheck = false;");
        ps.println();
        ps.println("	public static void startExplicitLockCheck() {");
        ps.println("		isExplicitLockCheck = true;");
        ps.println("	}");
        ps.println();
        ps.println("	public static _Tables_ getInstance() {");
        ps.println("		return (_Tables_)xdb.Xdb.getInstance().getTables();");
        ps.println("	}");
        ps.println();
        ps.println("	public _Tables_() {");
        for (Table table : lsTables) {
            ps.println("		add(" + table.getName() + ");");
        }
        ps.println("	}");
        ps.println();
        ps.println("	// visible in package");
        for (Table table : lsTables) {
            table.define(ps);
        }
        ps.println();
        ps.println("}");

        ps.close();
    }

    public String getName() {
        return name;
    }

    public String getLock() {
        return lock;
    }

    @Override
    public String toString() {
        return this.getName();
    }

    public Type getKeyType() {
        return keytype;
    }

    public Type getValueType() {
        return valuetype;
    }

    void compile(Xdb xdb) {
        keytype = Type.compile(key, null, null);
        if (!keytype.isConstant()) {
            throw new RuntimeException("table.key need a constant valuetype");
        }
        if (autoIncrement && !keytype.supportAutoIncrement()) {
            throw new RuntimeException("type of '" + key + "' unsupport autoIncrement");
        }
        valuetype = Type.compile(value, null, null);
        if (!(valuetype instanceof XBean) && !valuetype.isConstant()) {
            throw new RuntimeException("table-valuetype MUST be constant or xbean. table=" + name);
        }
    }

    void verify(Xdb xdb) {
        Capacity cap = new Capacity(capacity, "table." + this.getName());
        this.keytype.verifyCapacity(xdb, cap.extractKey());
        this.valuetype.verifyCapacity(xdb, cap.extractValue());
        cap.warnIf(null != cap.getCapacity(), "capacity is not required.");

        // setOwner
        this.keytype.addOwnerTable(this);
        this.valuetype.addOwnerTable(this);
    }

    boolean isNeedExplicitCheck() {
        return Main.explicitLockCheck && !Main.explicitLockIgnoreTables.contains(name);
    }

    void make() {
        if (valuetype.isAny() && !isMemory) {
            throw new RuntimeException("'" + value + "@" + name + "(isAny() && !isMemory)");
        }

        String classname = Main.toUpper1(name);
        PrintStream ps = Main.openTableFile(classname);

        String K = keytype.getBoxingName();
        String V = valuetype.getBoxingName();
        String KV = "<" + K + ", " + V + ">";

        ps.println("package xtable;");
        ps.println();
        ps.println("// typed table access point");
        ps.println("public class " + classname + " {");
        ps.println("	" + classname + "() {");
        ps.println("	}");
        ps.println();

        // explicitLockCheck
        if (isNeedExplicitCheck()) {
            ps.println("	private static void _explicitLockCheck(" + K + " key) {");
            ps.println("		if (!_Tables_.isExplicitLockCheck) return;");
            ps.println("		xdb.Lockey lockey = xdb.Lockeys.get(_Tables_.getInstance()." + name + ", key);");
            ps.println("		if (!lockey.isHeldByCurrentThread()) {");
            ps.println("			throw new IllegalStateException(\"check ExplicitLock Exception. lockey:[\" + lockey.toString());");
            ps.println("		}");
            ps.println("	}");
            ps.println();
        }

        if (this.autoIncrement) {
            ps.println("	public static xdb.util.AutoKey<" + K + "> getAutoKey() {");
            ps.println("		return _Tables_.getInstance()." + name + ".getAutoKey();");
            ps.println("	}");
            ps.println();
            ps.println("	public static " + K + " nextKey() {");
            ps.println("		return getAutoKey().next();");
            ps.println("	}");
            ps.println();
            ps.println("	public static " + K + " insert(" + V + " value) throws Exception {");
            ps.println("		Long next = nextKey();");
            ps.println("		insert(next, value);");
            ps.println("		return next;");
            ps.println("	}");
            ps.println();
        }

        // firstInsert
        ps.println("	public static void firstInsert(" + K + " key, " + V + " value) throws Exception {");
        if (!isMemory() && lock.equals("rolelock")) {
            ps.println("		if (!xdb.Xdb.getInstance().validateInsertRoleRecord(key)) {");
            ps.println("			throw new RuntimeException(\"can not insert rolelock refrence record while roleid is invalidate! k=\" + key);");
            ps.println("		}");
        }
        ps.println("		_Tables_.getInstance()." + name + ".firstInsert(key, value);");
        ps.println("	}");
        ps.println();

        // get
        ps.println("	public static " + V + " get(" + K + " key) throws Exception {");
        if (isNeedExplicitCheck()) {
            ps.println("		_explicitLockCheck(key);");
        }
        ps.println("		return _Tables_.getInstance()." + name + ".get(key);");
        ps.println("	}");
        ps.println();
        ps.println("	public static " + V + " get(" + K + " key, " + V + " value) throws Exception {");
        if (isNeedExplicitCheck()) {
            ps.println("		_explicitLockCheck(key);");
        }
        ps.println("		return _Tables_.getInstance()." + name + ".get(key, value);");
        ps.println("	}");
        ps.println();
        // insert delete update put putIfAbsent
        ps.println("	public static void insert(" + K + " key, " + V + " value) throws Exception {");
        if (!isMemory() && lock.equals("rolelock")) {
            ps.println("		if (!xdb.Xdb.getInstance().validateInsertRoleRecord(key)) {");
            ps.println("			throw new RuntimeException(\"can not insert rolelock refrence record while roleid is invalidate! k=\" + key);");
            ps.println("		}");
        }
        ps.println("		_Tables_.getInstance()." + name + ".insert(key, value);");
        ps.println("	}");
        ps.println();
        ps.println("	public static void delete(" + K + " key) throws Exception {");
        if (isNeedExplicitCheck()) {
            ps.println("		_explicitLockCheck(key);");
        }
        ps.println("		_Tables_.getInstance()." + name + ".delete(key);");
        ps.println("	}");
        ps.println();
        //		ps.println("	public static boolean update(" + K + " key, " + V + " value) {");
        //		ps.println("		return _Tables_.getInstance()." + name + ".update(key, value);");
        //		ps.println("	}");
        //		ps.println("");
        //		ps.println("	public static " + V + " put(" + K + " key, " + V + " value) {");
        //		ps.println("		return _Tables_.getInstance()." + name + ".put(key, value);");
        //		ps.println("	}");
        //		ps.println("");
        //		ps.println("	public static " + V + " putIfAbsent(" + K + " key, " + V + " value) {");
        //		ps.println("		return _Tables_.getInstance()." + name + ".putIfAbsent(key, value);");
        //		ps.println("	}");
        //		ps.println("");
        ps.println("	public static boolean add(" + K + " key, " + V + " value) throws Exception {");
        if (!isMemory() && lock.equals("rolelock")) {
            ps.println("		if (!xdb.Xdb.getInstance().validateInsertRoleRecord(key)) {");
            ps.println("			throw new RuntimeException(\"can not add rolelock refrence record while roleid is invalidate! k=\" + key);");
            ps.println("		}");
        }
        if (isNeedExplicitCheck()) {
            ps.println("		boolean ret = _Tables_.getInstance()." + name + ".add(key, value);");
            ps.println("		if (!ret) {");
            ps.println("			_explicitLockCheck(key);");
            ps.println("		}");
            ps.println("		return ret;");
        } else {
            ps.println("		return _Tables_.getInstance()." + name + ".add(key, value);");
        }
        ps.println("	}");
        ps.println();
        // remove
        ps.println("	public static boolean remove(" + K + " key) throws Exception {");
        if (isNeedExplicitCheck()) {
            ps.println("		_explicitLockCheck(key);");
        }
        ps.println("		return _Tables_.getInstance()." + name + ".remove(key);");
        ps.println("	}");
        ps.println();
        // cache
        ps.println("	public static xdb.TTableCache" + KV + " getCache() {");
        ps.println("		return _Tables_.getInstance()." + name + ".getCache();");
        ps.println("	}");
        ps.println();
        ps.println("	public static xdb.TTable" + KV + " getTable() {");
        ps.println("		return _Tables_.getInstance()." + name + ";");
        ps.println("	}");
        ps.println();
        valuetype.printTableSelectMethod(ps, K);
        ps.println("}");

        ps.close();
    }

    void createTableSql() {
        if (isMemory()) {
            return;
        }
        String createSQL = null;
        String clearSQL = null;
        if (Main.isUseJson()) {
            createSQL = "CREATE TABLE IF NOT EXISTS " + getName()
                    + "(k VARCHAR(255) NOT NULL PRIMARY KEY, v JSON NOT NULL) ENGINE=INNODB DEFAULT CHARSET=UTF8;";
        } else {
            createSQL = "CREATE TABLE IF NOT EXISTS " + getName()
                    + "(k VARCHAR(255) NOT NULL PRIMARY KEY, v MEDIUMBLOB NOT NULL) ENGINE=INNODB DEFAULT CHARSET=UTF8;";
        }
        clearSQL = "DROP TABLE IF EXISTS " + getName() + ";";
        LoggerFactory.getLogger("tablecreate").info(createSQL);
        LoggerFactory.getLogger("tableclear").info(clearSQL);
        LoggerFactory.getLogger("tableclear").info(createSQL);
    }

    void define(PrintStream ps) {
        String k = keytype.getBoxingName();
        String v = valuetype.getBoxingName();
        String t = "xdb.TTable<" + k + ", " + v + ">";

        ps.println("	" + t + " " + name + " = new " + t + "() {");
        ps.println("		@Override");
        ps.println("		public String getName() {");
        ps.println("			return " + Main.quote(name) + ";");
        ps.println("		}");
        ps.println();

        if (this.autoIncrement) {
            ps.println("		@Override");
            ps.println("		protected xdb.util.AutoKey<" + k + "> bindAutoKey() {");
            ps.println("			return getInstance().getTableSys().getAutoKeys().getAutoKey" + k + "(getName());");
            ps.println("		}");
            ps.println();
        }

        ps.println("		@Override");
        ps.println("		public OctetsStream marshalKey(" + k + " key) {");
        ps.println("			OctetsStream _os_ = new OctetsStream();");
        keytype.marshal(null, null, ps, "			", "key");
        ps.println("			return _os_;");
        ps.println("		}");
        ps.println();
        ps.println("		@Override");
        ps.println("		public OctetsStream marshalValue(" + v + " value) {");
        ps.println("			OctetsStream _os_ = new OctetsStream();");
        valuetype.marshal(null, null, ps, "			", "value");
        ps.println("			return _os_;");
        ps.println("		}");
        ps.println();

        ps.println("		@Override");
        ps.println("		public " + k + " unmarshalKey(OctetsStream _os_) throws MarshalException {");
        ps.println("			" + keytype.defineNoParent("key"));
        keytype.unmarshal(null, null, ps, "			", "key");
        ps.println("			return key;");
        ps.println("		}");
        ps.println();

        ps.println("		@Override");
        ps.println("		public " + v + " unmarshalValue(OctetsStream _os_) throws MarshalException {");
        ps.println("			" + valuetype.defineNoParent("value"));
        valuetype.unmarshal(null, null, ps, "			", "value");
        ps.println("			return value;");
        ps.println("		}");
        ps.println();
        ps.println("		@Override");
        ps.println("		public " + v + " newValue() {");
        ps.println("			" + valuetype.defineNoParent("value"));
        ps.println("			return value;");
        ps.println("		}");
        ps.println();
        ps.println("	};");
        ps.println();
    }

    public void printMeta(PrintStream ps, String prefix) {
        ps.println(prefix + "super.addTable(" + Main.quote(getName()) + ", " + Main.quote(this.isMemory ? "MEMORY" : "DB") + ", "
                + Main.quote(this.key) + ", " + this.autoIncrement + ", " + Main.quote(this.value) + ", " + Main.quote(this.capacity) + ");");
    }

    //-add by cjc 2009-9-14 for depends
    public void depends(Set<Type> types) {
        this.getValueType().depends(types);
        //cbean
        this.getKeyType().depends(types);
    }

    public boolean isMemory() {
        return isMemory;
    }

    public boolean isAutoIncrement() {
        return autoIncrement;
    }
}
